//#define FOG
#define LIGHT1
#define LIGHT2

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Oops.Xna.Framework.Viewpoint;

namespace Oops.Xna.Framework.Graphics
{
	/// <summary>
	/// Represents a parallel split shadow map effect with optional support for vertex colors, texturing and lighting.
	/// </summary>
	/// <remarks>
	/// Similar in usage as the <see cref="Microsoft.Xna.Framework.Graphics.BasicEffect"/> class.
	/// </remarks>
	public class PssmEffect : Effect
	{
		private const int MAX_SPLIT_COUNT = 3;
		private const int PCF_SAMPLE_COUNT = 9;

		private EffectTechnique createShadowTechnique;
		private EffectTechnique applyShadowTechnique;

		private EffectParameter worldEffectParameter;
		private EffectParameter viewEffectParameter;
		private EffectParameter projectionEffectParameter;

		private bool vertexColorEnabled;
		private EffectParameter diffuseColorEffectParameter;
		private EffectParameter emissiveColorEffectParameter;
		private EffectParameter specularColorEffectParameter;
		private EffectParameter specularPowerEffectParameter;
		private EffectParameter alphaEffectParameter;

		private EffectParameter eyePositionEffectParameter;
#if FOG
		private EffectParameter fogEnabledEffectParameter;
		private EffectParameter fogStartEffectParameter;
		private EffectParameter fogEndEffectParameter;
		private EffectParameter fogColorEffectParameter;
#endif	
		private bool diffuseTextureEnabled;
		private EffectParameter diffuseTextureEffectParameter;

		private bool lightingEnabled;
		private bool preferPerPixelLighting;
		private EffectParameter ambientLightColorEffectParameter;

		private PssmDirectionalLight light0;
#if LIGHT1
		private PssmDirectionalLight light1;
#endif
#if LIGHT2
		private PssmDirectionalLight light2;
#endif

		private Matrix shadowMapTextureScale;
		private EffectParameter shadowMapSizeEffectParameter;
		private EffectParameter shadowBiasEffectParameter;
		private EffectParameter shadowAlphaEffectParameter;
		private EffectParameter[] shadowMapTexturesEffectParameters = new EffectParameter[PssmEffect.MAX_SPLIT_COUNT];

		private int splitCount = PssmEffect.MAX_SPLIT_COUNT;
		private float inverseSplitCount = 1.0f / (float)PssmEffect.MAX_SPLIT_COUNT;
		private float splitSchemeLambda = 0.5f;
		private float[] splitDistances = new float[PssmEffect.MAX_SPLIT_COUNT + 1];
		private Matrix[] splitViewProjections = new Matrix[PssmEffect.MAX_SPLIT_COUNT];
		private EffectParameter splitCountEffectParameter;
		private EffectParameter splitDistancesEffectParameter;
		private EffectParameter splitViewProjectionsEffectParameter;

		private Vector2[] pcfSamples = new Vector2[PCF_SAMPLE_COUNT];
		private EffectParameter pcfSampleCountEffectParameter;
		private EffectParameter pcfSamplesEffectParameter;

		private EffectParameter shaderIndexEffectParameter;

		private DepthStencilBuffer currentDepthStencilBuffer;
		private RenderTarget2D[] shadowMapRenderTargets;
		private DepthStencilBuffer shadowMapDepthStencilBuffer;

		private Camera camera;
		private Matrix cameraProjection = Matrix.Identity;
		private float cameraFarPlaneDistance;

		private BoundingFrustum frustrum = new BoundingFrustum(Matrix.Identity);
		private Vector3[] frustumCorners = new Vector3[8];

		/// <summary>
		/// Initializes a new instance of the <see cref="PssmEffect"/> class.
		/// </summary>
		/// <param name="device">The graphics device that will create the effect.</param>
		/// <param name="cloneSource">The effect to clone.</param>
		public PssmEffect(GraphicsDevice device, Effect cloneSource) : base(device, cloneSource)
		{
			// Cache all effect techniques.
			this.createShadowTechnique = this.Techniques["CreateShadow"];
			this.applyShadowTechnique = this.Techniques["ApplyShadow"];

			// Cache shader index parameter.
			this.shaderIndexEffectParameter = this.Parameters["ShaderIndex"];

			// Cache all matrix parameters.
			this.worldEffectParameter = this.Parameters["World"];
			this.viewEffectParameter = this.Parameters["View"];
			this.projectionEffectParameter = this.Parameters["Projection"];

			// Cache all matrial parameters.
			this.diffuseColorEffectParameter = this.Parameters["DiffuseColor"];
			this.emissiveColorEffectParameter = this.Parameters["EmissiveColor"];
			this.specularColorEffectParameter = this.Parameters["SpecularColor"];
			this.specularPowerEffectParameter = this.Parameters["SpecularPower"];
			this.alphaEffectParameter = this.Parameters["Alpha"];

			// Cache all light parameters.
			this.eyePositionEffectParameter = this.Parameters["EyePosition"];
			this.ambientLightColorEffectParameter = this.Parameters["AmbientLightColor"];
#if FOG
			// Cache all fog parameters.
			this.fogEnabledEffectParameter = base.Parameters["FogEnabled"];
			this.fogStartEffectParameter = base.Parameters["FogStart"];
			this.fogEndEffectParameter = base.Parameters["FogEnd"];
			this.fogColorEffectParameter = base.Parameters["FogColor"];
#endif
			// Cache texture parameters.
			this.diffuseTextureEffectParameter = this.Parameters["DiffuseTexture"];

			// Create the directional lights owned instance.
			this.light0 = new PssmDirectionalLight(
				this.Parameters["Light0Direction"],
				this.Parameters["Light0DiffuseColor"],
				this.Parameters["Light0SpecularColor"]);
#if LIGHT1
			this.light1 = new PssmDirectionalLight(
				this.Parameters["Light1Direction"],
				this.Parameters["Light1DiffuseColor"],
				this.Parameters["Light1SpecularColor"]);
#endif
#if LIGHT2
			this.light2 = new PssmDirectionalLight(
				this.Parameters["Light2Direction"],
				this.Parameters["Light2DiffuseColor"],
				this.Parameters["Light2SpecularColor"]);
#endif
			// Cache shadow parameters.
			this.shadowMapSizeEffectParameter = this.Parameters["ShadowMapSize"];
			this.shadowBiasEffectParameter = this.Parameters["ShadowBias"];
			this.shadowAlphaEffectParameter = this.Parameters["ShadowAlpha"];
			for (int i = 0; i < PssmEffect.MAX_SPLIT_COUNT; i++)
			{
				this.shadowMapTexturesEffectParameters[i] = this.Parameters["ShadowMapTexture" + i.ToString()];
			}

			this.splitCountEffectParameter = this.Parameters["SplitCount"];
			this.splitDistancesEffectParameter = this.Parameters["SplitDistances"];
			this.splitViewProjectionsEffectParameter = this.Parameters["SplitViewProjections"];

			// TODO : Remember that we can multi-sample.
			// Cache percent closer filtering parameters.
			this.pcfSampleCountEffectParameter = this.Parameters["PCFSampleCount"];
			this.pcfSamplesEffectParameter = this.Parameters["PCFSamples"];

			// Initialize matrix settings.
			this.World = Matrix.Identity;

			// Initialize material settings.
			this.DiffuseColor = Vector3.One;
			this.SpecularColor = Vector3.One;
			this.EmissiveColor = Vector3.Zero;
			this.SpecularPower = 16f;
			this.Alpha = 1.0f;

			// Make sure the shader index is set.
			this.UpdateShaderIndex();

			// Initialize lighting settings.
			this.AmbientLightColor = new Vector3(0.525f, 0.525f, 0.525f);

			// Initialize the lights.
			this.light0.Enabled = false;
			this.light0.Direction = Vector3.Up;
			this.light0.DiffuseColor = Vector3.Zero;
			this.light0.SpecularColor = Vector3.Zero;
#if LIGHT1
			this.light1.Enabled = false;
			this.light1.Direction = Vector3.Up;
			this.light1.DiffuseColor = Vector3.Zero;
			this.light1.SpecularColor = Vector3.Zero;
#endif
#if LIGHT2
			this.light2.Enabled = false;
			this.light2.Direction = Vector3.Up;
			this.light2.DiffuseColor = Vector3.Zero;
			this.light2.SpecularColor = Vector3.Zero;
#endif
#if FOG
			// Initialize fog settings.
			this.FogEnabled = false;
			this.FogStart = 0.0f;
			this.FogEnd = 1.0f;
			this.FogColor = Vector3.Zero;
#endif
			// Initialize shadow settings.
			this.ShadowMapSize = 1024;
			this.ShadowBias = 0.0f;
			this.ShadowAlpha = 0.5f;
			this.SplitCount = PssmEffect.MAX_SPLIT_COUNT;

			// Initialize percent closer filtering settings.
			this.PcfSampleCount = PssmEffect.PCF_SAMPLE_COUNT;
		}

		/// <summary>
		/// Gets or sets the world matrix.
		/// </summary>
		public Matrix World
		{
			get { return this.worldEffectParameter.GetValueMatrix(); }
			set { this.worldEffectParameter.SetValue(value); }
		}

		// TODO : This might be made more generic.  I don't know if I want a reference to active camera here.
		// TODO : Effect uses AspectRatio, View, FieldOfView, Projection and NearPlanDistance.
		/// <summary>
		/// Gets or sets the current <see cref="Camera"/>.
		/// </summary>
		public Camera Camera
		{
			get { return this.camera; }
			set { this.camera = value; }
		}

		/// <summary>
		/// Enables use vertex colors for this effect.
		/// </summary>
		public bool VertexColorEnabled
		{
			get { return this.vertexColorEnabled; }
			set
			{
				this.vertexColorEnabled = value;
				this.UpdateShaderIndex();
			}
		}

		/// <summary>
		/// Gets or sets the diffuse color of this effect material. Value takes 0 to 1.
		/// </summary>
		public Vector3 DiffuseColor
		{
			get { return this.diffuseColorEffectParameter.GetValueVector3(); }
			set { this.diffuseColorEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the emissive color of the effect material.
		/// </summary>
		public Vector3 EmissiveColor
		{
			get { return this.emissiveColorEffectParameter.GetValueVector3(); }
			set { this.emissiveColorEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the specular color of this effect material.
		/// </summary>
		public Vector3 SpecularColor
		{
			get { return this.specularColorEffectParameter.GetValueVector3(); }
			set { this.specularColorEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the specular power of this effect material.
		/// </summary>
		public float SpecularPower
		{
			get { return this.specularPowerEffectParameter.GetValueSingle(); }
			set { this.specularPowerEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the alpha this effect material.
		/// </summary>
		public float Alpha
		{
			get { return this.alphaEffectParameter.GetValueSingle(); }
			set { this.alphaEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the ambient light color of this effect.
		/// </summary>
		public Vector3 AmbientLightColor
		{
			get { return this.ambientLightColorEffectParameter.GetValueVector3(); }
			set { this.ambientLightColorEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets the first directional light for this effect.
		/// </summary>
		public PssmDirectionalLight DirectionalLight0
		{
			get { return this.light0; }
		}
#if LIGHT1
		/// <summary>
		/// Gets the second directional light for this effect.
		/// </summary>
		public PssmDirectionalLight DirectionalLight1
		{
			get { return this.light1; }
		}
#endif
#if LIGHT2
		/// <summary>
		/// Gets the third directional light for this effect.
		/// </summary>
		public PssmDirectionalLight DirectionalLight2
		{
			get { return this.light2; }
		}
#endif
		/// <summary>
		/// Enables lighting for this effect.
		/// </summary>
		public bool LightingEnabled
		{
			get	{ return this.lightingEnabled; }
			set
			{
				this.lightingEnabled = value;
				this.UpdateShaderIndex();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating that per-pixel lighting should be used if it is available 
		/// for the current adapter. Per-pixel lighting is available if a graphics adapter supports 
		/// Pixel Shader Model 2.0.
		/// </summary>
		public bool PreferPerPixelLighting
		{
			get	{ return this.preferPerPixelLighting; }
			set
			{
				this.preferPerPixelLighting = value;
				this.UpdateShaderIndex();
			}
		}
#if FOG
		/// <summary>
		/// Enables fog for this effect.
		/// </summary>
		public bool FogEnabled
		{
			get { return this.fogEnabledEffectParameter.GetValueBoolean(); }
			set { this.fogEnabledEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the fog start distance.
		/// </summary>
		public float FogStart
		{
			get { return this.fogStartEffectParameter.GetValueSingle(); }
			set { this.fogStartEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the ending distance of fog.
		/// </summary>
		public float FogEnd
		{
			get { return this.fogEndEffectParameter.GetValueSingle(); }
			set { this.fogEndEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the fog color for this effect.
		/// </summary>
		public Vector3 FogColor
		{
			get { return this.fogColorEffectParameter.GetValueVector3(); }
			set { this.fogColorEffectParameter.SetValue(value); }
		}
#endif
		/// <summary>
		/// Enables textures for this effect.
		/// </summary>
		public bool DiffuseTextureEnabled
		{
			get	{ return this.diffuseTextureEnabled; }
			set
			{
				this.diffuseTextureEnabled = value;
				this.UpdateShaderIndex();
			}
		}

		/// <summary>
		/// Gets or sets a texture to be applied by this effect.
		/// </summary>
		public Texture2D DiffuseTexture
		{
			get { return this.diffuseTextureEffectParameter.GetValueTexture2D(); }
			set { this.diffuseTextureEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the shadow map size for this effect.
		/// </summary>
		public int ShadowMapSize
		{
			get { return this.shadowMapSizeEffectParameter.GetValueInt32(); }
			set
			{
				if (this.ShadowMapSize != value)
				{
					// Dispose of shadow map render target and stencil buffer.
					this.UnloadContent();

					// Assign the value to the effect parameter.
					this.shadowMapSizeEffectParameter.SetValue(value);

					// Create the shadow map texture scale matrix.
					float offset = 0.5f + (0.5f / (float)value);
					this.shadowMapTextureScale = new Matrix(
						0.5f, 0.0f, 0.0f, 0.0f,
						0.0f, -0.5f, 0.0f, 0.0f,
						0.0f, 0.0f, 1.0f, 0.0f,
						offset, offset, 0.0f, 1.0f);

					float texelSize = 1.0f / value;

					this.pcfSamples[00] = new Vector2(0.0f, 0.0f);
					this.pcfSamples[01] = new Vector2(0.0f, texelSize);
					this.pcfSamples[02] = new Vector2(texelSize, texelSize);
					this.pcfSamples[03] = new Vector2(texelSize, 0.0f);
					this.pcfSamples[04] = new Vector2(texelSize, -texelSize);
					this.pcfSamples[05] = new Vector2(0.0f, -texelSize);
					this.pcfSamples[06] = new Vector2(-texelSize, -texelSize);
					this.pcfSamples[07] = new Vector2(-texelSize, 0.0f);
					this.pcfSamples[08] = new Vector2(-texelSize, texelSize);

					// TODO : Uncomment for larger sampling area.  Update in fx as well.
					//this.pcfSamples[09] = new Vector2(-texelSize, texelSize * 2.0f);
					//this.pcfSamples[10] = new Vector2(0.0f, texelSize * 2.0f);
					//this.pcfSamples[11] = new Vector2(texelSize, texelSize * 2.0f);
					//this.pcfSamples[12] = new Vector2(texelSize * 2.0f, texelSize * 2.0f);
					//this.pcfSamples[13] = new Vector2(texelSize * 2.0f, texelSize);
					//this.pcfSamples[14] = new Vector2(texelSize * 2.0f, 0.0f);
					//this.pcfSamples[15] = new Vector2(texelSize * 2.0f, -texelSize);
					//this.pcfSamples[16] = new Vector2(texelSize * 2.0f, -texelSize * 2.0f);
					//this.pcfSamples[17] = new Vector2(texelSize, -texelSize * 2.0f);

					//this.pcfSamples[18] = new Vector2(0.0f, -texelSize * 2.0f);
					//this.pcfSamples[19] = new Vector2(-texelSize, -texelSize * 2.0f);
					//this.pcfSamples[20] = new Vector2(-texelSize * 2.0f, -texelSize * 2.0f);
					//this.pcfSamples[21] = new Vector2(-texelSize * 2.0f, -texelSize);
					//this.pcfSamples[22] = new Vector2(-texelSize * 2.0f, 0.0f);
					//this.pcfSamples[23] = new Vector2(-texelSize * 2.0f, texelSize);
					//this.pcfSamples[24] = new Vector2(-texelSize * 2.0f, texelSize * 2.0f);

					this.pcfSamplesEffectParameter.SetValue(this.pcfSamples);

					// Recreate the shadow map render target and stencil buffer.
					this.LoadContent();
				}
			}
		}

		/// <summary>
		/// Gets or sets the shadow bias for this effect.
		/// </summary>
		public float ShadowBias
		{
			get { return this.shadowBiasEffectParameter.GetValueSingle(); }
			set { this.shadowBiasEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the shadow alpha for this effect.
		/// </summary>
		public float ShadowAlpha
		{
			get { return this.shadowAlphaEffectParameter.GetValueSingle(); }
			set { this.shadowAlphaEffectParameter.SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the shadow split count for this effect.
		/// </summary>
		public int SplitCount
		{
			get { return this.splitCount; }
			set
			{
				// Clamp value to valid range.
				value = MathExtension.Clamp(value, 1, PssmEffect.MAX_SPLIT_COUNT);
				
				if (this.splitCount != value)
				{
					// Hold onto value.
					this.splitCount = value;
					this.splitCountEffectParameter.SetValue(value);

					// Create enough split view projections.
					this.splitViewProjections = new Matrix[this.splitCount];

					// Calculate the the reciprocal and dimension the split array.
					this.inverseSplitCount = 1.0f / (float)this.splitCount;
					this.splitDistances = new float[this.splitCount + 1];

					// Recreate the shadow map render target and stencil buffer.
					this.LoadContent();
				}
			}
		}

		/// <summary>
		/// Gets or sets split scheme lambda for this effect.
		/// </summary>
		public float SplitSchemeLambda
		{
			get { return this.splitSchemeLambda; }
			set { this.splitSchemeLambda = MathHelper.Clamp(value, 0.0f, 1.0f); }
		}

		// TODO : Not really used.
		public int PcfSampleCount
		{
			get { return this.pcfSampleCountEffectParameter.GetValueInt32(); }
			set { this.pcfSampleCountEffectParameter.SetValue(value); }
		}

		#region Effect Members

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				// Unload any graphic resources.
				this.UnloadContent();
			}

			// Call inherited method.
			base.Dispose(disposing);
		}

		public override Effect Clone(GraphicsDevice device)
		{
			// Create a cloned effect.
			PssmEffect effect = new PssmEffect(device, this)
			{
				preferPerPixelLighting = this.preferPerPixelLighting,
				lightingEnabled = this.lightingEnabled,
				diffuseTextureEnabled = this.diffuseTextureEnabled,
				vertexColorEnabled = this.vertexColorEnabled,
				ShadowMapSize = this.ShadowMapSize,
				SplitCount = this.SplitCount,
				SplitSchemeLambda = this.SplitSchemeLambda,
				Camera = this.Camera
			};

			// Update the shader index.
			effect.UpdateShaderIndex();

			// Copy the lighting information.
			effect.DirectionalLight0.Copy(this.DirectionalLight0);
#if LIGHT1
			effect.DirectionalLight1.Copy(this.DirectionalLight1);
#endif
#if LIGHT2
			effect.DirectionalLight2.Copy(this.DirectionalLight2);
#endif

			// Return the cloned effect.
			return effect;
		}

		#endregion

		/// <summary>
		/// Starts the effect.
		/// </summary>
		/// <param name="bounds">The bounds of the scene that should be shadowed.</param>
		public void BeginPssm(BoundingBox bounds)
		{
			// Do some initialization.
			this.CalculateCameraFarPlaneDistance(ref bounds);
			this.CalculateSplitDistances();
		}

		/// <summary>
		/// Stops the effect.
		/// </summary>
		public void EndPssm()
		{
			// Nothing to do.
		}

		/// <summary>
		/// Starts the shadow creation stage of the effect.
		/// </summary>
		/// <param name="index">The shadow split index to start.</param>
		public void BeginCreateShadow(int index)
		{
			// Get the near and far plane distance for the specified frustum split.
			float nearPlaneDistance = this.splitDistances[index];
			float farPlaneDistance = this.splitDistances[index + 1];

			// Calculate corner points of frustum split.        
			this.CalculateCameraSplitFrustum(nearPlaneDistance, farPlaneDistance);
			this.CalculateLightSplitProjection();
			
			// Hold onto the current depth stencil buffer.
			this.currentDepthStencilBuffer = this.GraphicsDevice.DepthStencilBuffer;
						
			// Assign the the shadow map's depth stencil buffer and render target to
			// the graphics device.
			this.GraphicsDevice.DepthStencilBuffer = this.shadowMapDepthStencilBuffer;
			this.GraphicsDevice.SetRenderTarget(0, this.shadowMapRenderTargets[index]);

			// Clear the render target.
			this.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, new Vector4(float.MaxValue), 1.0f, 0);

			// Assign the shadow creation technique.
			this.CurrentTechnique = this.createShadowTechnique;

			// Assign some internal effect parameters.
			this.viewEffectParameter.SetValue(this.light0.view);
			this.projectionEffectParameter.SetValue(this.light0.projection);

			// Hold onto the shadow map tensor.
			this.splitViewProjections[index] = this.light0.view * this.light0.projection * this.shadowMapTextureScale;
		}

		/// <summary>
		/// Stops the shadow creation stage of the effect.
		/// </summary>
		/// <param name="index">he shadow split index to stop.</param>
		public void EndCreateShadow(int index)
		{
			// Assign the previous stencil buffer and render target back to the graphics device.
			this.GraphicsDevice.DepthStencilBuffer = this.currentDepthStencilBuffer;
			this.GraphicsDevice.SetRenderTarget(0, null);

			// Assign the results to the shadow map for this split.
			this.shadowMapTexturesEffectParameters[index].SetValue(this.shadowMapRenderTargets[index].GetTexture());
		}

		/// <summary>
		/// Starts the shadow application stage of the effect.
		/// </summary>
		public void BeginApplyShadow()
		{
			// Assign the shadow application technique.
			this.CurrentTechnique = this.applyShadowTechnique;

			// Set some internal effect parameters.
			this.viewEffectParameter.SetValue(this.camera.View);
			this.projectionEffectParameter.SetValue(this.camera.Projection);

			this.splitDistancesEffectParameter.SetValue(this.splitDistances);
			this.splitViewProjectionsEffectParameter.SetValue(this.splitViewProjections);

			this.eyePositionEffectParameter.SetValue(Matrix.Invert(this.camera.View).Translation);
		}

		/// <summary>
		/// Stops the shadow application stage of the effect.
		/// </summary>
		public void EndApplyShadow()
		{
			// Nothing to do.
		}

		/// <summary>
		/// Enables default lighting for this effect.
		/// </summary>
		public void EnableDefaultLighting()
		{
			this.LightingEnabled = true;
			this.AmbientLightColor = new Vector3(0.05333332f, 0.09882354f, 0.1819608f);

			float rotation = MathHelper.ToRadians(135);
			Vector3 vector = new Vector3(1.0f, 0.9607844f, 0.8078432f);
			this.DirectionalLight0.DiffuseColor = vector;
			this.DirectionalLight0.Position = new Vector3((float)(-210.0f * Math.Sin(rotation)), 210.0f, (float)(-210.0f * Math.Cos(rotation)));
			this.DirectionalLight0.Direction = Vector3.Normalize(Vector3.Zero - this.light0.Position);
			this.DirectionalLight0.SpecularColor = vector;
			this.DirectionalLight0.Enabled = true;
#if LIGHT1
			this.DirectionalLight1.DiffuseColor = new Vector3(0.9647059f, 0.7607844f, 0.4078432f);
			this.DirectionalLight1.Direction = new Vector3(0.7198464f, 0.3420201f, 0.6040227f);
			this.DirectionalLight1.SpecularColor = Vector3.Zero;
			this.DirectionalLight1.Enabled = true;
#endif
#if LIGHT2
			vector = new Vector3(0.3231373f, 0.3607844f, 0.3937255f);
			this.DirectionalLight2.DiffuseColor = vector;
			this.DirectionalLight2.Direction = new Vector3(0.4545195f, -0.7660444f, 0.4545195f);
			this.DirectionalLight2.SpecularColor = vector;
			this.DirectionalLight2.Enabled = true;
#endif
		}

		/// <summary>
		/// Updates the shader index based on vertex color, texture state and lighting states.
		/// </summary>
		private void UpdateShaderIndex()
		{
			int index = 
				(this.vertexColorEnabled ? 1 : 0) | 
				(this.diffuseTextureEnabled ? 2 : 0) | 
				(this.lightingEnabled ? 4 : 0);

			index += this.lightingEnabled && this.preferPerPixelLighting ? 4 : 0;

			// Set the shader index effect parameter.
			this.shaderIndexEffectParameter.SetValue(index);
		}

		/// <summary>
		/// Creates and loads the necessary graphics resources for this effect.
		/// </summary>
		private void LoadContent()
		{
			// Make sure to dispose of current graphic resources.
			this.UnloadContent();

			// Get the shadow map size.
			int shadowMapSize = this.ShadowMapSize;

			// Determine the surface format of the shadow map render target.
			SurfaceFormat surfaceFormat = SurfaceFormat.Single;
			if (!GraphicsAdapter.DefaultAdapter.CheckDeviceFormat(
				DeviceType.Hardware,
				GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format,
				TextureUsage.Linear,
				QueryUsages.None,
				ResourceType.RenderTarget,
				surfaceFormat))
			{
				surfaceFormat = SurfaceFormat.HalfSingle;
				if (!GraphicsAdapter.DefaultAdapter.CheckDeviceFormat(
					DeviceType.Hardware,
					GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format,
					TextureUsage.Linear,
					QueryUsages.None,
					ResourceType.RenderTarget,
					surfaceFormat))
				{
					surfaceFormat = SurfaceFormat.Color;
				}
			}

			// Create the shadow map render targets.
			this.shadowMapRenderTargets = new RenderTarget2D[this.SplitCount];
			for (int i = 0; i < this.SplitCount; i++)
			{
				this.shadowMapRenderTargets[i] = new RenderTarget2D(
					this.GraphicsDevice,
					shadowMapSize,
					shadowMapSize,
					1,
					surfaceFormat);
			}

			// Determine the depth format of the shadow map depth stencil buffer.
			DepthFormat depthFormat = DepthFormat.Depth24Stencil8Single;
			if (!GraphicsAdapter.DefaultAdapter.CheckDepthStencilMatch(
				DeviceType.Hardware,
				GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format,
				surfaceFormat,
				depthFormat))
			{
				depthFormat = DepthFormat.Depth24Stencil8;
				if (!GraphicsAdapter.DefaultAdapter.CheckDepthStencilMatch(
					DeviceType.Hardware,
					GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format,
					surfaceFormat,
					depthFormat))
				{
					depthFormat = DepthFormat.Depth24;
				}
			}

			// Create the shadow map depth stencil buffers.
			this.shadowMapDepthStencilBuffer = new DepthStencilBuffer(
				this.GraphicsDevice,
				shadowMapSize,
				shadowMapSize,
				depthFormat);
		}

		/// <summary>
		/// Unloads the necessary graphics resources for this effect.
		/// </summary>
		private void UnloadContent()
		{
			// Dispose of the shadow map render target and stencil buffer.
			if (this.shadowMapRenderTargets != null)
			{
				for (int i = 0; i < this.shadowMapRenderTargets.Length; i++)
				{
					this.shadowMapRenderTargets[i].Dispose();
				}
			}
			if (this.shadowMapDepthStencilBuffer != null)
			{
				this.shadowMapDepthStencilBuffer.Dispose();
			}
		}

		private Vector3[] corners = new Vector3[8];

		/// <summary>
		/// Calculates the far plane distance.
		/// </summary>
		/// <param name="bounds">The bounds of the scene that should be shadowed.</param>
		private void CalculateCameraFarPlaneDistance(ref BoundingBox bounds)
		{
			// Find the most distance corner of the scene's bounds.
			float minZ = 0.0f;
			bounds.GetCorners(this.corners);
			for (int i = 0; i < 8; i++)
			{
				float z =
					this.corners[i].X * this.camera.View.M13 +
					this.corners[i].Y * this.camera.View.M23 +
					this.corners[i].Z * this.camera.View.M33 +
					this.camera.View.M43;

				if (z < minZ)
				{
					minZ = z;
				}
			}

			// Adjust the camera's far plane distance by the most distant z component.
			this.cameraFarPlaneDistance = this.camera.NearPlaneDistance - minZ;
		}

		/// <summary>
		/// Calculates the split distances for the parallel-splits along the camera's frustum.
		/// </summary>
		private void CalculateSplitDistances()
		{
			// Calculate the far/new plane distance ratio once.
			float farNearPlaneDistanceRatio = this.cameraFarPlaneDistance / this.camera.NearPlaneDistance;

			// Iterate over the splits.
			for (int i = 0; i < this.splitCount; i++)
			{
				// Calculate the logarithmic and uniform distance for the current split.
				float idm = i * this.inverseSplitCount;
				float logarithmic = (float)(this.camera.NearPlaneDistance * Math.Pow(farNearPlaneDistanceRatio, idm));
				float uniform = this.camera.NearPlaneDistance + (this.cameraFarPlaneDistance - this.camera.NearPlaneDistance) * idm;

				// Calculate the current split distance by scaling between the calculated 
				// logarithmic and uniform methods.
				this.splitDistances[i] = logarithmic * this.splitSchemeLambda + uniform * (1.0f - this.splitSchemeLambda);
			}

			// Just set the nearest and furthest distances to the camera's near and far plane distances.
			this.splitDistances[0] = this.camera.NearPlaneDistance;
			this.splitDistances[splitCount] = this.cameraFarPlaneDistance;
		}

		/// <summary>
		/// Calculate the the camera's frustum for the current split.
		/// </summary>
		/// <param name="nearPlaneDistance">The near plane distance of the split.</param>
		/// <param name="farPlaneDistance">The far plane distance of the split.</param>
		private void CalculateCameraSplitFrustum(float nearPlaneDistance, float farPlaneDistance)
		{
			Matrix projection = Matrix.CreatePerspectiveFieldOfView(
				this.camera.FieldOfView,
				this.camera.Viewport.AspectRatio,
				nearPlaneDistance,
				farPlaneDistance);

			this.frustrum.Matrix = this.camera.View * projection;
			this.frustrum.GetCorners(this.frustumCorners);
		}

		/// <summary>
		/// Calculates the view/projection matrix for the light for the current parallel-split.
		/// </summary>
		private void CalculateLightSplitProjection()
		{
			// Determine the light's projection matrix with the specified far plane distance maximum.
			Matrix.CreatePerspectiveFieldOfView(
				this.light0.FieldOfView,
				1.0f,
				this.light0.NearPlaneDistance,
				this.light0.farPlaneMaximum,
				out this.light0.projection);

			// Combine the light's view and projection for the loop below.
			Matrix lightViewProjection = this.light0.view * this.light0.projection;

			// Initialize the minimum and maximum component values.
			// Iterate on all frustrum corners.
			BoundingBox cropBB = BoundingBoxExtension.Empty;
			cropBB.Max.Z = 0.0f;
			for (int i = 0; i < 8; i++)
			{
				// Transform the frustrum corner by the light's combined view/projection matrix.
				Vector4 corner;
				Vector4.Transform(ref this.frustumCorners[i], ref lightViewProjection, out corner);

				float inverseW = 1.0f / corner.W;

				// Project the x and y components of the corner.
				corner.X *= inverseW;
				corner.Y *= inverseW;

				// Determine the x and y components minimum and maximum values.  Only the maximum of
				// z component is necessary.
				if (corner.X < cropBB.Min.X) cropBB.Min.X = corner.X;
				if (corner.X > cropBB.Max.X) cropBB.Max.X = corner.X;
				if (corner.Y < cropBB.Min.Y) cropBB.Min.Y = corner.Y;
				if (corner.Y > cropBB.Max.Y) cropBB.Max.Y = corner.Y;
				if (corner.Z < cropBB.Min.Z) cropBB.Min.Z = corner.Z;
				if (corner.Z > cropBB.Max.Z) cropBB.Max.Z = corner.Z;
			}

			// Clamp results.
			cropBB.Min.X = MathHelper.Clamp(cropBB.Min.X, -1.0f, 1.0f);
			cropBB.Max.X = MathHelper.Clamp(cropBB.Max.X, -1.0f, 1.0f);
			cropBB.Min.Y = MathHelper.Clamp(cropBB.Min.Y, -1.0f, 1.0f);
			cropBB.Max.Y = MathHelper.Clamp(cropBB.Max.Y, -1.0f, 1.0f);

			cropBB.Min.Z = 0.0f;

			// Calculate the real far distance plane of the light.  Add a little bit just
			// in case.
			//this.directionalLight.FarPlaneDistance = cropBB.Max.Z + this.directionalLight.NearPlaneDistance + 1.5f;

			// Adjust the light's projection matrix by the new far plane distance.
			//Matrix.CreatePerspectiveFieldOfView(
			//    this.directionalLight.FieldOfView,
			//    1.0f,
			//    this.directionalLight.NearPlaneDistance,
			//    this.directionalLight.FarPlaneDistance,
			//    out this.directionalLight.projection);

			float scaleX = 2.0f / (cropBB.Max.X - cropBB.Min.X);
			float offsetX = -0.5f * (cropBB.Max.X + cropBB.Min.X) * scaleX;

			float scaleY = 2.0f / (cropBB.Max.Y - cropBB.Min.Y);
			float offsetY = -0.5f * (cropBB.Max.Y + cropBB.Min.Y) * scaleY;
						
			float scaleZ = 1.0f / (cropBB.Max.Z - cropBB.Min.Z);
			float offsetZ = -cropBB.Min.Z * scaleZ;

			Matrix cropView = new Matrix(
				scaleX, 0.0f, 0.0f, 0.0f,
				0.0f, scaleY, 0.0f, 0.0f,
				0.0f, 0.0f, scaleZ, 0.0f,
				offsetX, offsetY, offsetZ, 1.0f);

			// Apply the cropping matrix to the light's projection.
			this.light0.projection *= cropView;

			// Modify the light's projection matrix for linearized depth.
			float inverseFarPlaneDistance = 1.0f / this.light0.farPlaneMaximum; // this.directionLight.FarPlaneDistance;
			this.light0.projection.M33 *= inverseFarPlaneDistance;
			this.light0.projection.M43 *= inverseFarPlaneDistance;
		}
	}
}
